/*

 Copyright 2012 Peter Dornbach.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
   
*/

package com.dornbachs.zebra.guitarear;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Vector;

import android.content.Context;

public class Challenge {
	public static Challenge getChallenge(Context context) {
		if (_challenge == null) {
			SoundPlayer player = new SoundPlayer();
			player.init(context);
			_challenge = new Challenge(player);
		}
		return _challenge;
	}

	public UserState getUserState() {
		return _userState;
	}

	public void setUserState(UserState userState) {
		_userState = userState;
	}

	public void tryPractice(Level level) {
		_tryLevel = level.clone();
		_numHits = 0;
		_numTries = 0;
		_isPractice = true;
		_previousRiffChords = new Vector<Vector<ChordShape>>();
	}
	
	public void tryImprove(Level level) {
		_tryLevel = level.clone();
		_numHits = 0;
		_numTries = 0;
		_isPractice = false;
		_previousRiffChords = new Vector<Vector<ChordShape>>();
	}

	public boolean finishedLevel() {
		return !_isPractice && _numHits >=
				NUM_RIFFS * _tryLevel.getNumChords();
	}
	
	public boolean successLevel() {
		if (!finishedLevel()) return false;
		return numMisses() <= maxMisses();
	}
	
	public void markSuccess() {
		_userState.addLevel(_tryLevel);
	}
	
	public int maxMisses() {
		return MAX_MISSES * _tryLevel.getNumChords();
	}
	
	public int numHits() {
		return _numHits;
	}

	public int numMisses() {
		return _numTries - _numHits;
	}
	
	public boolean playChallenge() {
		if (_currentRiff.play(_currentInstrument)) {
			_hasPlayedChallenge = true;
			return true;
		} else {
			return false;
		}
	}

	public boolean hasPlayedChallenge() {
		return _hasPlayedChallenge;
	}
	
	public List<String> getCandidates() {
		return _candidates.get(_currentChord);
	}

	public boolean playCandidate(String guess) {
		if (_candidatesAreChords) {
			Riff riff = Riff.singleChordRiff(_chordShapesByName.get(guess));
			return riff.play(_currentInstrument);
		} else {
			return false;
		}
	}

	public boolean tryCandidate(String guess) {
		if (_currentChord >= _solutions.size()) return false;
		_numTries++;
		if (guess.equals(_solutions.get(_currentChord))) {
			_currentChord++;
			_numHits++;
			return true;
		} else {
			return false;
		}
	}

	public int currentChord() {
		return _currentChord;
	}
	
	public boolean hasMoreChords() {
		return _currentChord < _solutions.size();
	}
	
	public boolean hasMoreThanOneChord() {
		return _solutions.size() > 1;
	}
	
	public void nextChallenge(int maxNumCandidates) {
		// Get the palette of chords & instruments.
		_currentInstrument = _player.getInstrument(_tryLevel.getInstrumentName());
		ChordShape[] chordShapesByLevel;
		if (_tryLevel.reduceChords()) {
			chordShapesByLevel = ChordShape.REDUCED_BY_LEVEL;
			_chordShapesByName = ChordShape.REDUCED_BY_NAME;
		} else {
			chordShapesByLevel = ChordShape.FULL_BY_LEVEL;
			_chordShapesByName = ChordShape.FULL_BY_NAME;
		}

		// Create the riff that is the solution of the current puzzle.
		Vector<ChordShape> solutionChordShapes = ChordShape.randomChordShapes(
				chordShapesByLevel, 
				_tryLevel.getChordShapeLevel(),
				_tryLevel.getNumChords(),
				_previousRiffChords);
		_currentRiff = Strumming.strum(_tryLevel.getStrummingLevel(), solutionChordShapes);
		_currentChord = 0;
		_hasPlayedChallenge = false;
		_previousRiffChords.add(solutionChordShapes);
		if (_previousRiffChords.size() > NUM_PREVIOUS_RIFFS) {
			_previousRiffChords.removeElementAt(0);
		}

		// Create the candidates that will be offered to the user.
		_solutions = new Vector<String>();
		_candidates = new Vector<Vector<String>>();
		if (_tryLevel.getWhatLevel() > 0) {
			// Exact chords. A different set of candidates for each chord in the riff.
			for (ChordShape solutionChordShape: solutionChordShapes) {
				// Start with n-1 random chords which are available at the current level.
				ReservoirSampler<String> candidateSampler = new ReservoirSampler<String>(
						maxNumCandidates - 1);
				for (ChordShape chord : chordShapesByLevel) {
					if (chord._level > _tryLevel.getChordShapeLevel()) {
						break;
					}
					if (chord != solutionChordShape) {
						candidateSampler.add(chord._name, 1.0);
					}
				}
				// Add the correct solution too :)
				_solutions.add(solutionChordShape._name);
				Vector<String> candidates = new Vector<String>(candidateSampler.getSamples());
				candidates.add(solutionChordShape._name);
				Collections.sort(candidates);
				_candidates.add(candidates);
			}
			_candidatesAreChords = true;
		} else {
			// Chord quality only. One candidate for each chord. Since the number
			// of chord qualities is limited, we can always add all candidates and
			// no random sampling is done.
			HashSet<String> candidateSet = new HashSet<String>();
			for (ChordShape chord : chordShapesByLevel) {
				if (chord._level > _tryLevel.getChordShapeLevel()) {
					break;
				}
				candidateSet.add(chord.getQuality());
			}
			Vector<String> candidates = new Vector<String>(candidateSet);
			Collections.sort(candidates);
			for (ChordShape c: solutionChordShapes) {
				_solutions.add(c.getQuality());
				_candidates.add(candidates);
			}
			_candidatesAreChords = false;
		}
	}

	private Challenge(SoundPlayer player) {
		_userState = new UserState();
		_player = player;
	}

	// The number of riffs that need to be solved to advance to the next level.
	private static final int NUM_RIFFS = 12;
	
	// Allow at most so many misses for MAX_RIFFS in order
	// to successfully complete a level.
	private static final int MAX_MISSES = 1;

	// Try to make new challenges different from previous ones. This is
	// the number of previous ones to compare against.
	private static final int NUM_PREVIOUS_RIFFS = 2;
	
	// What the user has achieved so far
	private UserState _userState;

	// The current level we are trying.
	private Level _tryLevel;
	private HashMap<String, ChordShape> _chordShapesByName;
	private int _numHits;
	private int _numTries;

	// The current challenge.
	private SoundPlayer.Instrument _currentInstrument;
	private Riff _currentRiff;
	private int _currentChord;
	private boolean _hasPlayedChallenge;
	private Vector<String> _solutions;
	private Vector<Vector<String>> _candidates;
	private Vector<Vector<ChordShape>> _previousRiffChords;
	private boolean _candidatesAreChords;
	private boolean _isPractice;

	private SoundPlayer _player;

	// Global state.
	private static Challenge _challenge;
}
